-
-
Notifications
You must be signed in to change notification settings - Fork 21.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Trait System to GDScript #97657
base: master
Are you sure you want to change the base?
Add Trait System to GDScript #97657
Conversation
Is there a specific reason you specified a If not, might I recommend a different return type for clarity? |
(Edited because I missed a part in the OP description) Fantastic start on this feature. Thank you! One comment: please use |
I'm not sure your reasoning lines up with your conclusion there, but I can't say I have much of a preference, what with it being a strictly cosmetic affair. |
This system seems very similar to the |
Abstract classes are still beholden to the class hierarchy: No class can inherit from two classes at a time. There is some value in having both of these, I suppose, but traits are far more powerful. |
See: Also, as DaloLorn said, these are independent features that can coexist together. Traits offer capabilities that classic inheritance cannot provide. |
This looks great, will traits be able to constrain typed collections (ie. Array[Punchable], Dictionary[String, Kickable]) ? |
Amazing that somebody cared to make this,but since the original proposal is shut down,here is some feedback
|
Considering work has already been made for signals, we should get to keep them too. (unless massive performance issues appear) |
I am a bit concerned on the performance of this in general, but that would be something that can be solved over time. I am really, really ecstatic about this. I agree. There's no reason to exclude signals from traits if the work has been done. |
36d7605
to
4088f53
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some nitpicks, mostly you should rename uses
with impl
everywhere.
As various others have suggested to use
The 6 characters
|
what would you abbreviate
Pretty sure non-native speakers are able to understand abbreviations, by your logic
traits aren't exactly beginner stuff, when someone starts with a language they learn they might learn traits ,but for gamedev you don't learn certain stuff until you get the basics/become a casual programmer , when i was a unity developer, i didn't learn about interfaces (which are extremely similar to traits) until i had advanced enough and realised i need some other solution to inheritance
this makes no sense? let's take a look at some example code: class_name Door
extends AnimatableBody3D
impl Interactable what would "implies" mean in a progammer context?, "impels" isn't even abbreviated correctly, "implant"? seriously?, "implode" would be a function for gameplay
previous points still matter, also rust uses the |
Is using a separate file extension necessary? And if not, would it be better to stick to .gd? From a UX perspective it seems a lot simpler and easier not to. |
Extensions can be useful for quick search, filter, etc., without the need to check the content of the file nor adding extra prefix/suffix to file names (so it's better in UX terms), also can help other tools like the filesystem to implement custom icons. |
Put me down as another vote in favor of "implements", for what it's worth. I'm indifferent on "implements" versus "uses", but I'm not nearly so indifferent on "impl" versus "implements": The majority of Adriaan's concerns have not been addressed to my satisfaction. |
I think |
I agree on "impl" been a very confusing keyword. |
To resolve the class reference error: Compile godot, then run |
f27a5d9
to
8d2b91c
Compare
Nice catch, that was a bug. It is now fix and have write a test for it. trait SomeTrait:
static var some_static_var = 0
static var other_static_var = 0
static var third_static_var = 0
static func some_static_func():
print("some static func")
static func other_static_func():
print("other static func")
static func third_static_func():
print("third static func")
class SomeClass:
uses SomeTrait
# Overridden static variable
var other_static_var = 1 # using 'static' keyword is optional
static var third_static_var = 1
# Overridden static function
func other_static_func(): # using 'static' keyword is optional
print("overridden other static func")
static func third_static_func():
print("overridden third static func")
func _ready():
print(SomeClass.some_static_var)
print(SomeClass.other_static_var)
print(SomeClass.third_static_var)
SomeClass.some_static_func()
SomeClass.other_static_func()
SomeClass.third_static_func()
print("ok")
If static function/variable is declared in a trait, when redeclared/overridden in class should using 'static' keyword is optional ? (currently it is)
This is not correct traits are not object and should not be instanced or called, I will look for way to better communicate this to prevent misuse. |
Personally, I strongly feel that the class MyClass:
uses MyTrait
static var some_static_var = 2 # inherited from MyTrait
# OR
some_static_var = 2 # also inherited from MyTrait The Simply having |
I pulled the recent changes to verify the static variable behavior, and while it looks like it's closer to what is expected (at least to me), I think I may have found an inconsistency with it. I used this code: extends Node2D
trait HasStatic:
static var static_var = 3
class UsingClass:
uses HasStatic
class DifferentUsingClass:
uses HasStatic
func _ready():
UsingClass.static_var = 2
print("HasStatic's value: ", HasStatic.static_var)
print("UsingClass's value: ", UsingClass.static_var)
print("DifferentUsingClass's value: ", DifferentUsingClass.static_var) This prints out:
I would expect it to print out:
Does this sound like unexpected behavior, or am I missing something with how static variables are expected to work in traits? |
e010b36
to
4b9bac0
Compare
Fixed and now has a test.
This is illegal, now you get an error, traits should be considered as templates and can not be objects ,hence, not directly access to its member, calling or instancing.
I decided to go with this remaining optional, however, you get a warning. same applies on overriding variables that use |
This simple code fails: trait TestTrait extends Node:
pass
func test_func(node: Node):
if node is TestTrait:
pass
This works: func test_func(node: Node):
var traiter := node as TestTrait
if traiter:
pass |
I can double-check on my end, but I thought the syntax for inner traits would be: trait TestTrait:
extends Node ? Edit: Regardless of which syntax is correct (or if both are), my variation gets the same error: trait TestTrait2:
extends Node
func test_func(node: Node):
if node is TestTrait2:
pass
|
Some editor bugs:
godot.windows.editor.dev.x86_64_SVznbLcOmV.mp4EDIT: In a script that tries to use this trait I get this error:
In another project I had error that variables were not found when trying to refer to variables from trait, not sure how to reproduce it. The bugged scripts: extends Node
uses PlayerWeapon
func _ready() -> void:
attack_type = 5 # If the script manages to parse, this will result in runtime error. EDIT2: |
In your variation the trait extends RefCounted (default base class), so the error is correct. EDIT: |
@@ -288,12 +317,25 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must | |||
if (!found) { | |||
return TTR("Invalid extension."); | |||
} | |||
if (!match) { | |||
if (!match && _extention_update_selected_language(p.get_extension()) != OK) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!match && _extention_update_selected_language(p.get_extension()) != OK) { | |
if (!match && _extension_update_selected_language(p.get_extension()) != OK) { |
return language->validate_path(p); | ||
} | ||
|
||
Error ScriptCreateDialog::_extention_update_selected_language(const String &p_extention) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error ScriptCreateDialog::_extention_update_selected_language(const String &p_extention) { | |
Error ScriptCreateDialog::_extension_update_selected_language(const String &p_extension) { |
Error ScriptCreateDialog::_extention_update_selected_language(const String &p_extention) { | ||
for (int i = 0; i < language_list.size(); i++) { | ||
ScriptLanguage *lang = language_list[i]; | ||
if (p_extention == lang->get_extension()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (p_extention == lang->get_extension()) { | |
if (p_extension == lang->get_extension()) { |
@@ -100,6 +102,7 @@ class ScriptCreateDialog : public ConfirmationDialog { | |||
void _use_template_pressed(); | |||
bool _validate_parent(const String &p_string); | |||
String _validate_path(const String &p_path, bool p_file_must_exist); | |||
Error _extention_update_selected_language(const String &p_extention); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error _extention_update_selected_language(const String &p_extention); | |
Error _extension_update_selected_language(const String &p_extension); |
</brief_description> | ||
<description> | ||
See [GDScript] or [@GDScript] instead. | ||
Helper Script to store Traits for GDScript, its not attachable to objects [method Object.set_script].Saved with the [code].gdt[/code] extension, for GDScript programming language external Traits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Helper Script to store Traits for GDScript, its not attachable to objects [method Object.set_script].Saved with the [code].gdt[/code] extension, for GDScript programming language external Traits. | |
Script resource to store traits for GDScript created with the [code].gdt[/code] file extension, allows defining and requiring common methods across multiple classes, along with anything that you would see inside of a GDScript class (signals, classes, etc.). |
<description> | ||
See [GDScript] or [@GDScript] instead. | ||
Helper Script to store Traits for GDScript, its not attachable to objects [method Object.set_script].Saved with the [code].gdt[/code] extension, for GDScript programming language external Traits. | ||
Note that it can not be instanced. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that it can not be instanced. | |
[b]Note:[/b] Unlike [GDScript], this resource cannot create instances (see [method GDScript.new]) due to its abstract nature. |
<?xml version="1.0" encoding="UTF-8" ?> | ||
<class name="GDTrait" inherits="GDScript" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> | ||
<brief_description> | ||
A script implemented in the GDScript programming language. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A script implemented in the GDScript programming language. | |
Script resource to store traits for GDScript. |
since the og proposal is locked, i am forced to state my opinion here, i believe traits shouldn't have variables, i can go overkill and say signals don't deserve to be in traits, but i feel (not know) there may be cases where signals are needed, but variables? pretty sure only C++ does this and C++ is known for tons of bad stuff, if variables are desperately needed, once can use a function getter and setter and otherwise should be in classes, for static stuff, i am not really sure how static stuff would even work, static stuff is related to the type, and traits are types, and we would need reflection/metaprogramming, to better explain trait Interactable:
pass
func f():
if i is Interactable:
i.interact()
if typeof(i).traits.has(Interactable):
typeof(i).static_interact() get it now? if a type requires a static method, chances are you would put it in the class quick edit: the prev code is a mock code of what is gdscript needs to have to use static functions correctly and is meant to showcase how useless and troublesome it will be |
signals are helpful in interfaces/traits since you can do something like: trait_name Destroyable:
signal destroyed(self: Destroyable) |
@Dynamic-Pistol you can create a discussion to talk about this I don't want to extend the off topic but I want to add that the implications of variables in traits are huge and can add a lot of flexibility, just think on how these can be used in resources and other data types, to extend and to patch them |
GDScript Trait System
Based on the discussion opened in:
The GDScript trait system allows traits to be declared in separate
.gdt
files or within atrait SomeTrait
block.Traits facilitate efficient code reuse by enabling classes to share and implement common behaviors, reducing duplication and promoting maintainable design.
Syntax Breakdown
Declaring Traits
Traits can be defined globally or within classes.
In a
.gdt
file, declare a global trait usingtrait_name GlobalTrait
at the top.trait
used for inner traits.Traits can contain all class members: enums, signals, variables, constants, functions and inner classes.
Example:
Using Traits in Classes
Use the
uses
keyword after theextends
block, followed by the path or global name of the trait.Traits can include other traits but do not need to implement their unimplemented functions. The implementation burden falls on the class using the trait.
Example:
Creating Trait files.
How Traits Are Handled
Cases
When a class uses a trait, its handled as follows:
1. Trait and Class Inheritance Compatibility:
The trait's inheritance must be a parent of the class's inheritance (compatible), but not the other way around, else an error occurs. Also note traits are pass down by inheritance, If a class is for instance "SomeTrait" also it here classes will be so.
Example:
2. Used Traits Cohesion:
When a class uses various traits, some traits' members might shadow other traits members ,hence, an error should occur when on the trait relative on the order it is declared.
3. Enums, Constants, Variables, Signals, Functions and Inner Classes:
These are copied over, or an error occurs if they are shadowed.
4. Extending Named Enums:
Named enums can be redeclared in class and have new enum values.
Note that unnamed enum are just copied over if not shadowing.
5. Overriding Variables:
This is allowed if the type is compatible and the value is changed.
Or only the type further specified. Export, Onready, Static state of trait variables are maintained. Setter and getter is maintained else overridden (setters parameters same and the ).
6. Overriding Signal:
This is allowed if parameter count are maintained and the parameter types is compatible by further specified from parent class type.
Example:
7. Overriding Functions:
Allowed if parameter count are maintained, return types and parameter types are compatible, but the function body can be changed. Static and rpc state of trait functions are maintained.
8. Unimplemented (Bodyless) Functions:
The class must provide an implementation. If a bodyless function remains unimplemented, an error occurs. Static and rpc state of trait functions are maintained.
9. Extending Inner Classes:
Inner classes defined in used trait can be redeclared in class and have new members provide not shadow members declared inner class declared in trait. Allow Member overrides for variables, Signals and function while extending Enum and its' Inner Classes.
Example:
Special Trait Features
10. Trait can use other Traits:
A trait is allows to use another trait except it does not alter members of the trait it is using by overriding or extending.
However, cyclic use of traits (TraitA uses TraitB and TraitB uses TraitA) is not permitted and will result in error.
11. Tool Trait:
if one trait containing the
@tool
keyword is used it converts classes (except inner classes) and traits using it into tool scripts.12. File-Level Documentation:
Member documentation is copied over from trait else overridden.
System Implementation Progress
as
)is
).gdt
files unattachable to objects/nodesBugsquad edit: